/** @file    ModemDslImpl.cpp
 *  @author  Alessandro Polo
 *  @version $Id: ModemDslImpl.cpp 2866 2010-08-07 15:01:48Z alex $
 *  @brief
 * File containing methods for the ModemDslImpl class.
 * The header for this class can be found in ModemDslImpl.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/

 #include "ModemDslImpl.h"

 #include <core/FilePath.h>
 #include <stdio.h>

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// system-dependent declarations
int get_stat(void);
void get_ppp_stats( pppStats& stats_out, const char* interf);
void get_ip_address( pppStats& stats_out, const char* interf);
void get_time( pppStats& stats_out);
//void update_stat( pppStats& stats_out, int sec_span = 1 );
int is_online( const char* interf );
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
using namespace std;
using namespace wosh;
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

ModemDslImpl::ModemDslImpl( BundleGeneric& bundle )
	: BundleGenericWorker(bundle) {
	this->modemListener = NULL;
	this->controlScript = "";
	this->autoRefreshFrequency = 0;
	this->pingCheckAddress = "";
	this->pppInterface = "";
	memset(&this->stats, '\0', sizeof(this->stats));
 }

ModemDslImpl::~ModemDslImpl() {
	Log(LOG_VERBOSE, ":~ModemDslImpl(): Destroying.." );
	if ( isThreadRunning() ) {
		quitThread(10000);
	 }
 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////// THREAD RUN

void ModemDslImpl::runThread() {
	long counter = 0;
	this->configMux.lockForRead();
	long freq = this->autoRefreshFrequency;
	this->configMux.unLock();

	getStats(false, 0);
	ThreadImpl::sleepForSec(1);
	getStats(false, 1);
	ThreadImpl::sleepForSec(1);
	getStats(false, 1);

	while(this->running) {
		ThreadImpl::sleepForSec(1);
		setThreadAlive();

		this->configMux.lockForRead();
		if ( freq != this->autoRefreshFrequency ) {
			freq = this->autoRefreshFrequency;
			this->configMux.unLock();
			getStats(false, 0);
			counter = 0;
			continue;
		 }
		this->configMux.unLock();

		if ( freq <= 0 ) continue;
		counter++;
		if ( counter < freq ) continue;
		counter = 0;

		if ( !this->running ) break;
		// update stats and raise event
		getStats(true, freq);
	 }
 }

////////////////////////////////////////////////////////////////////////////////////////////////// THREAD RUN
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////// EVENTS

void ModemDslImpl::raiseEvent( wosh::interfaces::devices::Modem::CONNECTION_STATE state ) {
	this->configMux.lockForRead();
	IModemDslListener* handler = this->modemListener;
	this->configMux.unLock();

	if ( handler != NULL )
		handler->modem_ConnectionState( this, state );
 }

////////////////////////////////////////////////////////////////////////////////////////////////////// EVENTS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////// MODEM METHODS

int ModemDslImpl::callControlScript( const std::string& params ) {
	FilePath script;
	this->configMux.lockForRead();
	script.set(this->controlScript);
	this->configMux.unLock();
	if ( !script.isFile() ) {
		Log(LOG_CRITICAL, "callControlScript() FAILED : Invalid script %s (or not found)", script.getPathLocal().c_str() );
		return -1001;
	 }
	std::string command = script.getPathLocal() + " " + params;
	int ret = system( command.c_str() );
	return ret;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT ModemDslImpl::checkConnected() {
	this->configMux.lockForRead();
	std::string pingadd = this->pingCheckAddress;
	this->configMux.unLock();

	getStats(false, -1);
	int ret = callControlScript( "check " + pingadd );

	if ( ret == -1001 ) {
		raiseEvent( wosh::interfaces::devices::Modem::STATE_UNKNOWN );
		Log(LOG_WARNING, "checkConnected() : UNKNOWN, (script returned %d)", ret );
		return WRET_ERR_ILLEGAL_USE;
	 }
	else if ( ret == 0 ) {
		raiseEvent( wosh::interfaces::devices::Modem::STATE_CONNECTED );
		Log(LOG_VERBOSE, "checkConnected() : CONNECTED, (script returned 0)" );
		return WRET_OK;
	 }
	else if ( ret == 1 ) {
		raiseEvent( wosh::interfaces::devices::Modem::STATE_DISCONNECTED );
		Log(LOG_INFO, "checkConnected() : DISCONNECTED, (script returned 1)" );
		return WRET_OK;
	 }
	else {
		raiseEvent( wosh::interfaces::devices::Modem::STATE_UNKNOWN );
		Log(LOG_WARNING, "checkConnected() : DISCONNECTED, (script returned %d)", ret );
	 }
	return WRET_ERR_INTERNAL;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT ModemDslImpl::connect( std::string params ) {
	int ret = callControlScript( "connect " + params );

	if ( ret == -1001 ) {
		raiseEvent( wosh::interfaces::devices::Modem::STATE_UNKNOWN );
		Log(LOG_WARNING, "connect() : UNKNOWN, (script returned %d)", ret );
		return WRET_ERR_ILLEGAL_USE;
	 }
	else if ( ret == 0 ) {
		raiseEvent( wosh::interfaces::devices::Modem::STATE_CONNECTED );
		Log(LOG_VERBOSE, "connect() : CONNECTED, (script returned 0)" );
		return WRET_OK;
	 }
	else {
		raiseEvent( wosh::interfaces::devices::Modem::STATE_UNKNOWN );
		Log(LOG_CRITICAL, "connect() : FAILED, (script returned %d)", ret );
	 }
	return WRET_ERR_INTERNAL;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT ModemDslImpl::disconnect() {
	int ret = callControlScript( "disconnect" );

	if ( ret == -1001 ) {
		raiseEvent( wosh::interfaces::devices::Modem::STATE_UNKNOWN );
		Log(LOG_WARNING, "disconnect() : UNKNOWN, (script returned %d)", ret );
		return WRET_ERR_ILLEGAL_USE;
	 }
	else if ( ret == 0 ) {
		raiseEvent( wosh::interfaces::devices::Modem::STATE_DISCONNECTED );
		Log(LOG_VERBOSE, "disconnect() : CONNECTED, (script returned 0)" );
		return WRET_OK;
	 }
	else {
		raiseEvent( wosh::interfaces::devices::Modem::STATE_UNKNOWN );
		Log(LOG_CRITICAL, "disconnect() : FAILED, (script returned %d)", ret );
	 }
	return WRET_ERR_INTERNAL;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT ModemDslImpl::reconnect() {
	int ret = callControlScript( "reconnect" );

	if ( ret == -1001 ) {
		raiseEvent( wosh::interfaces::devices::Modem::STATE_UNKNOWN );
		Log(LOG_WARNING, "reconnect() : UNKNOWN, (script returned %d)", ret );
		return WRET_ERR_ILLEGAL_USE;
	 }
	else if ( ret == 0 ) {
		raiseEvent( wosh::interfaces::devices::Modem::STATE_DISCONNECTED );
		Log(LOG_VERBOSE, "reconnect() : CONNECTED, (script returned 0)" );
		return WRET_OK;
	 }
	else {
		raiseEvent( wosh::interfaces::devices::Modem::STATE_UNKNOWN );
		Log(LOG_CRITICAL, "reconnect() : FAILED, (script returned %d)", ret );
	 }
	return WRET_ERR_INTERNAL;
 }

/////////////////////////////////////////////////////////////////////////////////////////////// MODEM METHODS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void ModemDslImpl::setModemListener( IModemDslListener* Listener ) {
	MutexLockerWrite mL(&configMux);
	this->modemListener = Listener;
 }

void ModemDslImpl::setRefreshFrequency( unsigned long value ) {
	MutexLockerWrite mL(&configMux);
	this->autoRefreshFrequency = value;
 }

void ModemDslImpl::setControlScript( const std::string& value ) {
	MutexLockerWrite mL(&configMux);
	this->controlScript = value;
 }

void ModemDslImpl::setPingCheckAddress( const std::string& value ) {
	MutexLockerWrite mL(&configMux);
	this->pingCheckAddress = value;
 }

void ModemDslImpl::setPppInterface( const std::string& value ) {
	MutexLockerWrite mL(&configMux);
	this->pppInterface = value;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

long ModemDslImpl::getRefreshFrequency() const {
	MutexLockerRead mL(&configMux);
	return this->autoRefreshFrequency;
 }

std::string ModemDslImpl::getControlScript() const {
	MutexLockerRead mL(&configMux);
	return this->controlScript;
 }

std::string ModemDslImpl::getPingCheckAddress() const {
	MutexLockerRead mL(&configMux);
	return this->pingCheckAddress;
 }

std::string ModemDslImpl::getPppInterface() const {
	MutexLockerRead mL(&configMux);
	return this->pppInterface;
 }

IModemDslListener* ModemDslImpl::getModemListener() {
	MutexLockerRead mL(&configMux);
	return this->modemListener;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT ModemDslImpl::getStats( bool raiseEvent, int sec_span ) {
	this->configMux.lockForRead();
	std::string interface = this->pppInterface;
	this->configMux.unLock();
	if ( interface == "" )
		return WRET_ERR_ILLEGAL_USE;

	this->stats.online = is_online( interface.c_str() );

	// Update the information
	get_ppp_stats( this->stats, interface.c_str() );
	get_ip_address( this->stats, interface.c_str() );
	get_time( this->stats );

	if ( sec_span == 0 ) { // reset speed and dynamic stats
		this->stats.rx_bytes_comp = 0;
		this->stats.tx_bytes_comp = 0;
		this->stats.curr_speed = 0.0;
		this->stats.curr_speed = 0.0;
		this->stats.top_speed = 0;
	 }
	else if ( sec_span > 0 ) { // update speed and dynamic stats
		if( this->stats.rx_bytes_comp == 0 )
			this->stats.rx_bytes_comp = this->stats.rx_bytes;
		if( this->stats.tx_bytes_comp == 0 )
			this->stats.tx_bytes_comp = this->stats.tx_bytes;

		double bps[2];
		bps[0] = this->stats.rx_bytes - this->stats.rx_bytes_comp;
		bps[1] = this->stats.tx_bytes - this->stats.tx_bytes_comp;

		// ks variable gets the speed in k/s of the connection
		this->stats.curr_speed = ( (float)(bps[0] + bps[1]) / 1024 ) / sec_span;

		if( this->stats.curr_speed > this->stats.top_speed )
			this->stats.top_speed = this->stats.curr_speed;

		this->stats.rx_bytes_comp = this->stats.rx_bytes;
		this->stats.tx_bytes_comp = this->stats.tx_bytes;
	 }
	if ( !raiseEvent ) return WRET_OK;

	// raise (callback) event
	this->configMux.lockForRead();
	IModemDslListener* handler = this->modemListener;
	this->configMux.unLock();

	if ( handler != NULL )
		handler->modem_StatsUpdate( this, this->stats );

	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// BASED on pppstatus 0.4.2 by Gabriel Montenegro

#include <arpa/inet.h>
#include <net/ppp_defs.h>
#include <net/if.h> 
#include <net/if_ppp.h>
#include <netinet/in.h>
#include <sys/stat.h> // void get_time( pppStats& stats_out)

#define PATH_PROC_NET_ROUTE "/proc/net/route"

int ppp_s = -1;
char var_run_pid[30] = "/var/run/ppp0.pid"; 


// GET_STATISTICS - Gets RX/TX Packets, Bytes and Errors
void get_ppp_stats( pppStats& stats_out, const char* interf) {
	struct ppp_stats ppp_cur;
	static int ppp_opened = 0;
	if ( !ppp_opened ) { // If not opened yet... Open the PPP device
		memset( &ppp_cur, 0, sizeof(ppp_cur) );
		if ( (ppp_s = socket(AF_INET, SOCK_DGRAM, 0)) < 0 )
			return;// -1;
		ppp_opened = 1;
	 }

	struct ifpppstatsreq req;
	memset(&req, 0, sizeof(req));  
	req.stats_ptr = (char *)&req.stats;

	strcpy(req.ifr__name, interf);
	if (ioctl(ppp_s, SIOCGPPPSTATS, &req) >= 0)
		ppp_cur = req.stats;

	stats_out.tx_packets = ppp_cur.p.ppp_opackets;
	stats_out.rx_packets = ppp_cur.p.ppp_ipackets;   
	stats_out.rx_bytes = ppp_cur.p.ppp_ibytes;
	stats_out.tx_bytes = ppp_cur.p.ppp_obytes;
	stats_out.rx_errors = ppp_cur.p.ppp_ierrors;
	stats_out.tx_errors = ppp_cur.p.ppp_oerrors;
}

char* unknown_IP = "Unknown";

// IP_ADDRESS - Gets local IP address
void get_ip_address( pppStats& stats_out, const char* interf) {
	struct ifreq ifr;
	struct sockaddr_in *skt;

	strcpy(ifr.ifr_name, interf);

	if( ioctl(ppp_s, SIOCGIFADDR, &ifr) < 0 ) { ; }

	skt = (struct sockaddr_in*)&ifr.ifr_addr;

	if ( skt->sin_addr.s_addr )
		stats_out.ip_addr_rtrn = inet_ntoa(skt->sin_addr); // BUGGY
	else
		stats_out.ip_addr_rtrn = unknown_IP;
 }

// CONNECTION TIME - 
void get_time( pppStats& stats_out) {
	// Get the current time
	stats_out.current_time = time(NULL);

	struct stat st;
	// Get the time that PPP run
	if ( !stat(var_run_pid, &st) ) 
		stats_out.connect_time = st.st_mtime;

	// Online Time
	stats_out.online_time_span = stats_out.current_time - stats_out.connect_time;

	stats_out.online_sec = stats_out.online_time_span % 60;
	stats_out.online_min = (stats_out.online_time_span / 60) % 60; 
	stats_out.online_hour = (stats_out.online_time_span / 3600) % 24;
	stats_out.online_days = (stats_out.online_time_span / (3600 * 24)) % 365;
 }

// IS_ONLINE - Check if the connection is active
int is_online( const char* interf ){
	int rtrn = 0;
	char chk[128];
	FILE *routefile;

	struct stat st;
	// Get the time that PPP run
	if( !stat(var_run_pid, &st) ) {
		if(st.st_size) {
			if( (routefile = fopen(PATH_PROC_NET_ROUTE, "r")) == NULL )
				return 0;
				//close_ppps("Error! Unable to open %s\nCheck in your Kernel Configuration if /proc filesystem is supported\n\n", PATH_PROC_NET_ROUTE);

			while( fgets(chk, 128, routefile) ) {
				if ( strstr(chk, interf) ) {
					rtrn = 1;
					break;
				 }
			 }
			fclose(routefile);	     
		}
		else
			rtrn = 0;
	}
   return rtrn;
}

/*
void update_stat( pppStats& stats_out, int sec_span ) {
	if( stats_out.rx_bytes_comp == 0 )
		stats_out.rx_bytes_comp = stats_out.rx_bytes;
	if( stats_out.tx_bytes_comp == 0 )
		stats_out.tx_bytes_comp = stats_out.tx_bytes;

	double bps[2];
	bps[0] = stats_out.rx_bytes - stats_out.rx_bytes_comp;
	bps[1] = stats_out.tx_bytes - stats_out.tx_bytes_comp;


	// ks variable gets the speed in k/s of the connection
	stats_out.curr_speed = ( (float)(bps[0] + bps[1]) / 1024 ) / sec_span;

	if( stats_out.curr_speed > stats_out.top_speed )
		stats_out.top_speed = stats_out.curr_speed;

	stats_out.rx_bytes_comp = stats_out.rx_bytes;
	stats_out.tx_bytes_comp = stats_out.tx_bytes;

//	stats_out.rx_packets_comp = stats_out.rx_packets;
//	stats_out.tx_packets_comp = stats_out.tx_packets;
 }

*/ 




